package com.nbhatti.simplemaps.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalSplitPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.VerticalSplitPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.maps.client.InfoWindowContent; 
import com.google.gwt.maps.client.MapWidget; 
import com.google.gwt.maps.client.control.LargeMapControl; 
import com.google.gwt.maps.client.event.MarkerClickHandler;
import com.google.gwt.maps.client.geom.LatLng; 
import com.google.gwt.maps.client.overlay.Marker; 
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.maps.client.geocode.*;

import java.util.LinkedList;
import java.util.ListIterator;

//An Entity class to represent 1 Map query and its result
class MapEntry {
	private String mAddress;   //query
	private double mLatitude;  //result
	private double mLongitude; //result
	private long mAccuracy;    //accuracy of result 1 for low 8 for high
	
	public String getAddress() {
		return mAddress;
	}
	public double getLatitude() {
		return mLatitude;
	}
	public double getLongitude() {
		return mLongitude;
	}
	public long getAccuracy() {
		return mAccuracy;
	}
	public MapEntry(String add, double lat, double lon, int acc) {
		this.mAddress = add;
		this.mLatitude = lat;
		this.mLongitude = lon;
		this.mAccuracy = acc;
	}
}

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 * GWT generates the JS from this code and calls the onModuleLoad
 * every time referesh is hit.
 */
public class SimpleMaps implements EntryPoint {
	//GUI
	//input UI
	private TextArea mAddressInputBox = new TextArea();
	private Button mCoordButton = new Button("Geo-Coords");
	private Button mUploadButton = new Button("Upload");
	private Button mDownloadButton = new Button("Save");
	private VerticalPanel mInputButtonPanel = new VerticalPanel();
	private HorizontalPanel mInputPanel = new HorizontalPanel();
	//output UI
	private FlexTable mMapDataDisplay = new FlexTable();
	private MapWidget mMapWidget;
	//layout panel. Couldn't get other layout panels to work any better
	//need to figure out what generates a span instead of div
	private HorizontalSplitPanel mHSP1 = new HorizontalSplitPanel();
	//private DockLayoutPanel mHSP1 = new DockLayoutPanel(Unit.EM);
	private VerticalSplitPanel mVSP = new VerticalSplitPanel();
	//private DockLayoutPanel  mVSP = new DockLayoutPanel(Unit.EM);
	//misc
	private Button mMsgOK = new Button("OK");

	//Data model
	private LinkedList<MapEntry> mMapData = new LinkedList<MapEntry>();
	
	public void onModuleLoad() {
		//set up MAP widget
		mMapWidget = new MapWidget(); 		    
		mMapWidget.addStyleName("map");
		mMapWidget.setHeight("400px");
		mMapWidget.setWidth("400px");
		//mMapDataDisplay.setWidth("400px"); 
		//mMapDataDisplay.setSize("400px", "500px"); 
		// Add some controls for the zoom level 
		mMapWidget.addControl(new LargeMapControl()); 
		//Input UI
		mInputButtonPanel.add(mCoordButton);
		mInputButtonPanel.add(mUploadButton);
		this.mUploadButton.setEnabled(false);//unused
		mInputButtonPanel.add(mDownloadButton);
		this.mDownloadButton.setEnabled(false);//unused
		mInputPanel.add(mInputButtonPanel);
		mInputPanel.add(mAddressInputBox);
		//horizontally split the display between Map and Map data display table
		mHSP1.setSplitPosition("50%");
		mHSP1.add(mMapWidget);
		mHSP1.add(this.mMapDataDisplay);
		//vertically split UI between input and output
		mVSP.setSplitPosition("70%");
		mVSP.add(mHSP1);
		mVSP.add(mInputPanel);
		//init the map data display
		loadMapList();
		refreshMaps();
		//on button press get new data and wait for query results
		mCoordButton.addClickHandler(new ClickHandler() {
			 public void onClick(ClickEvent event) {
				//The MAPs API that gives Geo coordinates
				Geocoder geocodes = new Geocoder();
				//get input address 
				String addresses = mAddressInputBox.getText();
				String[] adds = addresses.split("\n");
				final int querySize = adds.length;
				mMapData.clear();    //delete existing data
				//make queries
				for (int i = 0; i < querySize; ++i) {
					//the callback object
					ConsumeResult callback = new ConsumeResult(adds[i]);
					geocodes.getLocations(adds[i], callback);
				}
			 }
		});
		// Add the map to the HTML host page 
		RootLayoutPanel.get().add(mVSP);
	}
	//simply creates the header for Map Data Display
	private void loadMapList() {
		
		mMapDataDisplay.setText(0, 0, "Address");
		mMapDataDisplay.setText(0, 1, "Latitude");
		mMapDataDisplay.setText(0, 2, "Longitude");
		mMapDataDisplay.setText(0, 3, "Accuracy");
		//mMapDataDisplay.getColumnFormatter().setWidth(0, "10");
		mMapDataDisplay.getColumnFormatter().setWidth(1, "10");
		mMapDataDisplay.getColumnFormatter().setWidth(2, "10");
		mMapDataDisplay.getColumnFormatter().setWidth(3, "10");
		mMapDataDisplay.getRowFormatter().addStyleName(0, "mapListHeader");
		mMapDataDisplay.addStyleName("mapList");
		mMapDataDisplay.getCellFormatter().addStyleName(0, 1, "mapListNumericColumn");
		mMapDataDisplay.getCellFormatter().addStyleName(0, 2, "mapListNumericColumn");
		mMapDataDisplay.getCellFormatter().addStyleName(0, 3, "mapListNumericColumn");
	}
	//displays the map queries and their answers in the FlexTable
	private void refreshMaps(){
		ListIterator<MapEntry> it = mMapData.listIterator();
		int row = 1;
		while(it.hasNext()) {
			MapEntry me = it.next();
			mMapDataDisplay.setText(row, 0, new String("\"" + me.getAddress().trim()+ "\""));
			mMapDataDisplay.setText(row, 1, Double.toString(me.getLatitude()));
			mMapDataDisplay.setText(row, 2, Double.toString(me.getLongitude()));
			mMapDataDisplay.setText(row, 3, Long.toString(me.getAccuracy()));
			mMapDataDisplay.getRowFormatter().addStyleName(0, "mapListHeader");
			mMapDataDisplay.addStyleName("mapList");
			mMapDataDisplay.getCellFormatter().addStyleName(0, 1, "mapListNumericColumn");
			mMapDataDisplay.getCellFormatter().addStyleName(0, 2, "mapListNumericColumn");
			mMapDataDisplay.getCellFormatter().addStyleName(0, 3, "mapListNumericColumn");
			++row;
		}		
	}
	//This class implement the LocationCallback interface
	//It does 2 things
	//- adds an entry to mMapData
	//- update the map widget with "bubbles" to show location each bubble will say its coords
	class ConsumeResult implements LocationCallback {
		private String address;
		public ConsumeResult(String add) {
			address = add;
		}
		@Override
		public void onFailure(int statusCode) {
			final DialogBox dbg = new DialogBox();
			dbg.add(mMsgOK);
			mMsgOK.addClickHandler(new ClickHandler() {
				@Override
				public void onClick(ClickEvent event) {
					dbg.hide();
				}
			});
			dbg.setText("Could not find address: " + address);
			dbg.setPopupPosition(100, 100);
			dbg.show();
		}

		@Override
		public void onSuccess(JsArray<Placemark> locations) {
			// TODO Auto-generated method stub
		    for (int i = 0; i < locations.length(); i++) {
		        Placemark pm = locations.get(i);
		        LatLng ll = pm.getPoint();
		        double longi = ll.getLongitude();
		        double lati = ll.getLatitude();
				final String msg = new String(Double.toString(longi) + ", " + Double.toString(lati));
				//make a mapEntry to mapData
				MapEntry me = new MapEntry(address, lati, longi, pm.getAccuracy());
				mMapData.add(me);
				//Add a bubble to mapWidget with click handler to display coords
				Marker mm = new Marker(ll);
				mm.addMarkerClickHandler(new MarkerClickHandler(){
					@Override
					public void onClick(MarkerClickEvent event) {
						// TODO Auto-generated method stub				
						mMapWidget.getInfoWindow().open(((Marker)event.getSource()).getLatLng(),
						new InfoWindowContent(msg));		
					}					
				});
				mMapWidget.addOverlay(mm); 
				mMapWidget.getInfoWindow().open(ll, new InfoWindowContent(msg)); 						
		    }
		    //Should have been called using DeferedCommand but that didn't work
		    refreshMaps();
		}
	}
}